Forum des exercices du projet Zuul

Exercice 7.47

  
 
Avatar Denis BUREAU
Exercice 7.47
par Denis BUREAU, mardi 12 mai 2015, 11:21
 

ln the processCommand method in Game, there is a sequence of if statements to dispatch commands when a command word is recognized.
This is not a very nice design, since every time we add a command, we have to add a case here in this if statement. Can you improve this design?
Design the classes so that handling of commands is more modular, and new commands can be added more easily. Implement it. Test it.

S'inspirer de la conception montrée dans le projet zuul-even-better ci-joint et dont on peut lire les documentations utilisateur et programmeur.

Attention ! Bien lire la discussion ci-dessous (notamment la réponse du 19 novembre 2013) pour éviter toute régression.
Tout recompiler (Tools/Rebuild Package) : il ne doit subsister aucun avertissement.

Ensuite, peu de modifications structurelles devraient avoir lieu ; il est donc possible de commencer à introduire dans son jeu tous les éléments du scénario complet (lieux, items, images, suppléments, ...).

Partie optionnelle :
Cet exercice peut également vous inspirer si vous souhaitez reconcevoir la gestion de différents types de pièces, d'items, de personnages, ...

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, mardi 19 novembre 2013, 14:54
 

Un étudiant a écrit :

Bonjour,
Dans le cadre de l'exercice 7.47 visant à intégrer la conception de zuul-even-better, je me heurte à un problème : je ne vois pas comment combiner l'abstract Command avec les enums CommandWords.
Pour moi, si chaque commande a sa classe, l'enum ne sert plus.
Ai-je raison ? Si oui, supprime-t-on la conception des enums ? Si non, pourquoi, et comment combiner les 2 ?
Cordialement

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, mardi 19 novembre 2013, 15:14
 

Excellente question !

D'abord d'un point de vue général, il est bon de poser ce genre de question plutôt que de faire n'importe quoi; vous avez également pu constater que souvent dans les exercices précédents, une nouvelle version de Zuul vous était donnée comme exemple de mise en oeuvre d'un point particulier, mais qu'elle ne reprenait pas toutes les améliorations qui avaient été apportées à d'autres aspects du jeu.

Venons-en maintenant à l'exercice 7.47. Tout d'abord, c'est probablement un des exercices les plus difficiles de ce projet. C'est également celui qui demande le plus de modifications dans le code déjà écrit. J'espère qu'en lisant le chapitre 7 vous en comprendrez tout l'intérêt.

Alors, doit-on supprimer le long switch sur les commandes pour le remplacer par un simple appel de méthode (judicieusement redéfinie différemment pour chaque commande) ? OUI

Donc, doit-on supprimer les enum ? NON
Chaque String tapée est d'abord transformée en un CommandWord (enum), et une HashMap permet de retrouver le second à partir de la première. Ensuite, à chaque CommandWord est associée une Command qui contient la fameuse méthode à exécuter, et une HashMap permet de retrouver la seconde à partir du premier.
Cette conception permet donc de séparer la mécanique du jeu (CommandWord -> Command) de son interface textuelle (String -> CommandWord) en utilisant une seule instruction : "exécute la méthode associée à la Command associée au CommandWord associé à la String tapée".
Bien entendu, l'interface graphique peut court-circuiter une étape si elle fournit directement le CommandWord plutôt que la String.

Bon travail.

Attention ! zuul-even-better n'utilise pas de for-each mais des itérateurs ; il n'y a aucune raison de modifier votre code si vous avez utilisé des for-each à chaque fois que c'était possible.

Avatar Victor OU
Re: Exercice 7.47
par Victor OU, dimanche 1 décembre 2013, 17:52
 

Bonjour,

je suis actuellement bloqué dans cet exercice car dans la classe Parser, la méthode getCommand() doit renvoyer une commande, or la classe Command étant abstraite, on ne peut donc plus l'instancier...

J'ai pensé à récupérer séparément le CommandWord et le String composant une commande, et de les entrer en paramètre pour les méthodes demandant normalement un paramètre de type Command. Mais je ne pense pas que cela soit correct.

A part cela je ne vois vraiment pas comment faire contourner ce problème.

Pourriez-vous me donner une indication?

Cordialement.

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, dimanche 1 décembre 2013, 18:45
 

Il faut d'abord bien relire ma réponse à la 1ère question ci-dessus pour comprendre que depuis les exercices sur les enum, nous disposons de 2 HashMap : une String -> CommandWord et une CommandWord -> Command.

Cela nous permet donc de récupérer très facilement l'objet Command à partir de la String tapée.

(objet Command ne veut évidemment pas dire que l'on instancie la classe Command puisqu'elle est abstraite, mais qu'il s'agit d'un objet d'une sous-classe de Command)

Avatar Gauthier NOTTRET
Re: Exercice 7.47
par Gauthier NOTTRET, mardi 3 décembre 2013, 19:42
 

Bonsoir.

Suite à la lecture de l'exercice et des réponses données, deux problèmes se posent pour moi.

Si je veux conserver l'enum (qui fait double-emploi avec les sous-classes d'abstract command, ce qui cause toute la difficulté de cet exercice), je dois passer par une forme de Command générale, ce qui se faisait jusqu'à cet exercice.

Poblème (déjà pointé dans le message précedent), on ne peut plus instancier de command. Utiliser un command word : ok, mais que fait-on du SecondWord ? S'il s'agit de passer de string à {commandword + secondword} à command, le tout dans la même méthode, je commence à sérieusement douter de l'utilité de l'opération, je pense donc que cette solution n'est pas celle attendue.

Ma question principale est donc : faut-il faire le double passage string -> CW + SW -> command dans la même méthode ? Sinon, passer CW et SW simultanément d'une méthode à l'autre nécessite une classe contenant ses deux élements, mais là on revient à la solution du Command pas abstract, utilisé jusqu'ici ...

que faire ?

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, mardi 3 décembre 2013, 23:10
 

> Si je veux conserver l'enum (qui fait double-emploi avec les sous-classes d'abstract command, ce qui cause toute la difficulté de cet exercice),
>
??? Je ne vois pas en quoi cela fait double emploi. Peut-être voulez-vous parler du switch plutôt que des enum ?
Dans ce cas, relisez ma réponse à la première question posée sur cet exercice.

> Poblème (déjà pointé dans le message précedent), on ne peut plus instancier de command.
>
Pourquoi est-ce gênant de ne pas pouvoir instancier la classe Command ? Ce que nous voulons, c'est créer de vraies commandes qui ont chacune leur rôle, telle que GoCommand ou HelpCommand. Ces commandes doivent être créées au fur et à mesure de l'écriture de la 2èmeenum.

> Utiliser un command word : ok, mais que fait-on du SecondWord ? S'il s'agit de passer de string à {commandword + secondword} à command, le tout dans la même méthode, je commence à sérieusement douter de l'utilité de l'opération, je pense donc que cette solution n'est pas celle attendue.
>
Ne confondez pas la String aCommandWord du début avec l'enum CommandWord de maintenant !
Pour savoir comment traiter le second mot, regardez le Parser et la méthode setSecondWord de Command ...

Avatar Emilie ROUX
Re: Exercice 7.47
par Emilie ROUX, jeudi 2 juin 2016, 13:42
 

Boujour,

Quelle syntaxe doit-on utiliser pour créer une HashMap <CommandWord, Command>(); puis ajouter des éléments?

J'ai essayé

       this.aCommands = new HashMap <CommandWord, Command>(); 

       this. aCommands.put(GO, new GoCommand());

mais le compilateur m'indique "cannot find symbol - variable GO"...

 

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, jeudi 2 juin 2016, 15:19
 

Le compilateur vous dit qu'il ne trouve pas la variable GO, sans s'apercevoir qu'il s'agit d'une constante énumérée ...

Regardez dans le constructeur de la classe CommandWords du projet zuul-with-enums-v1 quelle notation employer pour que votre instruction ne génère plus d'erreur.

Ensuite, regardez le constructeur de la classe CommandWords du projet zuul-with-enums-v2 pour voir comment faire une boucle qui traite toutes les commandes sans les nommer une à une, ce qui vous évitera de chercher à désigner la commande GO en particulier.

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, lundi 16 décembre 2013, 09:53
 

Un étudiant a écrit :

Pour la méthode execute de chaque commande j'ai choisi de mettre en paramètre un Command et un GameEngine, est-ce une conception possible ou faut-il que je change?

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, lundi 16 décembre 2013, 09:57
 

Il faudrait voir comment vous avez écrit tout le reste pour donner un avis plus définitif, mais a priori, je ne vois pas pourquoi passer un paramètre Command à une méthode d'une sous-classe de Command ...

Pour GameEngine, pourquoi pas ? Il faut voir si le paramètre Player ne suffit pas, selon la façon dont vous avez prévu de récupérer la référence vers la UserInterface.

Avatar Kokulan SUBRAMANIAM
Re: Exercice 7.47
par Kokulan SUBRAMANIAM, samedi 31 mai 2014, 23:04
 

Bonsoir,

J'ai un problème que je n'arrive pas à résoudre. J'ai changé ma classe Command en classe Abstraite et j'ai créé différentes sous-classes( pour l'instant 3 comme dans le zuul-even-better). J'ai fait tous les changements dans différentes classes. 

Lorsque je lance mon jeu, et  que je tape aide, quitter ou go sans second mot, les commandes sont exécutées. Mais lorsque je tape go avec un second mot par exemple Nord, il ne reconnait pas la commande. Donc je voudrais savoir s'il faut faire encore une sous-classe de GoCommand pour les différentes directions, ou est-ce possible d'extraire la commande de la string tapée  pour que le jeu reconnaisse la commande et ensuite d'extraire le second mot et l'appliquer à la méthode qui dirige le joueur ?

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, dimanche 1 juin 2014, 09:32
 

Il n'y a pas besoin de créer de sous-classe de GoCommand puisque vous voyez qu'elle gère bien le 'secondWord'.
N'auriez-vous pas repris par erreur l'ancien Parser utilisant un Scanner au lieu de conserver celui utilisant la String provenant de l'entryField ?

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, dimanche 1 juin 2014, 23:50
 

L'étudiant a répondu :

Vous aviez raison , mon Passer n'étais pas bon et il me manquais une Hashmap<CommandWord , Command>. Maintenant que tout est réglé. Un autre problème survient , c'est l'affichage. Par exemple dans ma sous-classe HelpCommand je dois faire afficher plusieurs String. Dans le code donné, il y a plusieurs S.o.p mais je ne veux pas les faire afficher dans le terminal mais seulement dans le panel. Je me suis dit que je pourrais créer un attribut gui de type UserInterface et l'initialiser comme dans le GameEngine. Est-ce une bonne façon de faire ? Ou est-ce qu'il y aurais une autre façon ? Si oui pourriez-vous me donner quelques indications.
Merci

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, dimanche 1 juin 2014, 23:53
 

Je ne vois pas d'autre moyen effectivement que de mémoriser une référence vers la GUI.

Par contre, il y a de nombreuses possibilités pour le faire, dans Command ou dans certaines sous-classes, en static ou à la création de l'instance, à chaque exécution, ...

Choisissez la solution qui vous semble la plus raisonnable.

Avatar Chelsy Anne KASSAVALOO
Re: Exercice 7.47
par Chelsy Anne KASSAVALOO, dimanche 14 décembre 2014, 12:13
 

Bonjour,

J'ai créé toutes les nouvelles classes relatives à chaque commande mais certaines de ces classes ont besoin de méthodes présentes dans 

GameEngine (comme par exemple la méthode interpretCommand pour la Commande Test). 

Je ne vois vraiment pas comment faire pour avoir accès à ces méthodes.

Merci.

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, dimanche 14 décembre 2014, 17:35
 

À chaque appel de la méthode execute, ou mieux, à la création des commandes (uniquement celles qui le nécessitent), vous pourriez passer une référence vers le GameEngine.

Vous pourriez aussi le récupérer à partir du Player auquel vous avez accès ...

Avatar Chelsy Anne KASSAVALOO
Re: Exercice 7.47
par Chelsy Anne KASSAVALOO, lundi 15 décembre 2014, 19:33
 

Monsieur, je comprends ce que vous voulez faire mais je ne vois pas comment le faire.

- Lorsque je crée les commandes je suis dans l'enum CommandWord où à un CommandWord j'ai associé le command word et la Command mais je n'ai pas accès au GameEngine à moins que je me sois trompé et que la création des commandes ne se faisait pas ici.

- Dans l'autre possibilité, on a effectivement accès au Player qui est passé en paramètre mais je ne vois pas comment récupérer le GameEngine à partir du Player ( il n'y à rien dans le Player qui nous le permet).

Merci 

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, mardi 16 décembre 2014, 10:07
 

Dans les deux cas, il faut ajouter quelque chose : soit passer le GameEngine au constructeur de certaines commandes, soit ajouter au Player un accesseur retournant le GameEngine.

Ou bien encore, passer le GameEngine à execute() et récupérer le Player par un accesseur du GameEngine.

Avatar Nicolas LEROUX
Re: Exercice 7.47
par Nicolas LEROUX, lundi 22 décembre 2014, 21:30
 

Bonsoir, j'ai plusieurs questions par rapport à cette exercice. 

Tout d'abord je ne comprends pas vraiment l'intérêt de créer deux HashMap s plutôt qu'une seule comme illustré dans Zuul-even-better. De plus je ne vois pas vraiment où mettre la deuxième si ce n'est dans CommandWords avec la première. Est-ce juste dans un soucis de "bonne programmation" ?

Ensuite comme vous l'avez suggéré je préfère envoyer GameEngine en paramètre plutôt que Player pour execute(). Cependant je me retrouve à utiliser beaucoup de getters à l'intérieur de mes fonctions... Est-ce que c'est un problème ? Je me pose la question car pour l'implémentation de Player cela m'embêtais déjà et j'ai essayé de réduire leur nombre au maximum.

Enfin j'avais l'habitude dans les méthodes GoRoom et Back d'appeller Look pour afficher les lieux lorsqu'on se déplace d'une pièce à une autre (idem pour les téléportations etc). Or maintenant que les méthodes associées aux commandes sont disjointes il faudrait créer un objet LookCommand et l'éxecuter pour afficher les lieux. Est-ce que c'est envisageable ou est-ce qu'il y a une meilleur manière pour éviter la duplication de code ?

Avatar Chelsy Anne KASSAVALOO
Re: Exercice 7.47
par Chelsy Anne KASSAVALOO, mardi 23 décembre 2014, 14:41
 

- La HashMap te sert a retrouver la classe Command correspondant à la commande que l'utilisateur à tapé.

- Je pense que utiliser la méthode printLocationInfo qui se trouve GameEngine serait plus simple.

- après j'ai aussi plein de getters que j'utilise dans chaque fonction mais je pense qu'on est obligé.

Voila j'espère que sa t'as aidé

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, vendredi 26 décembre 2014, 16:19
 

Pour compléter la réponse ci-dessus, la 2ème HashMap sert à obtenir l'indépendance entre la String que l'on tape et la commande qui est déclenchée. Il n'y aura qu'un seul endroit à modifier si on décide de remplacer la commande "aller" par "va", par exemple. Par ailleurs, pour diminuer les appels de getters, ne pouvez-vous pas stocker certaines références dans des variables locales ?

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, lundi 12 janvier 2015, 10:58
 

Un étudiant a écrit :

Bonjour,
je vous envoie ce message pour avoir une petite précision sur abstract Command.
En fait, je me demandais si je devais supprimer toutes les méthodes ayant en paramètre un objet Command de la classe Player et GameEngine et ensuite de les réécrire dans leur classe command respective qui hérite d'abstract Command?

par exemple pour la méthode go:

-> au lieu de faire: *dans la classe GoCommand:

public boolean execute (final Player pPlayer)
{
pPlayer.walk(this);
}

*et dans la classe Player avoir la méthode:

public void walk(final Command pCmd)
{
//code
}
-> j'aurais juste: *dans GoCommand:
public boolean execute (final Player pPlayer)
{
//code de la fonction walk qui était dans la classe Player
}
*et dans la classe Player:
je n'aurais plus cette méthode

Merci pour votre attention,
Cordialemen,

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, lundi 12 janvier 2015, 11:04
 

La réponse est un peu un mélange de vos 2 solutions :

- dans GoCommand, on doit gérer la commande : que faire s'il y a ou pas un second mot, et par exemple, demander au Player de changer de pièce

- dans Player, on doit gérer une action du joueur : par exemple, changer de pièce

Inspirez-vous du projet zuul-even-better et des réponses aux questions déjà posées sur cet exercice.

Avatar Antoine BARROY
Re: Exercice 7.47
par Antoine BARROY, dimanche 3 janvier 2016, 18:43
 

Bonjour a vous, 

Est ce que la méthode execute doit absolument renvoyer un booléen ? Dans zuul-even-better c'est assez simple pour eux comme ils utilisent System.println() dans leurs classes **Command..

Et en essayant de faire l'exercice, j'ai, comme il est dit plus haut, beaucoup d'accesseurs.. Seulement est ce correct d'avoir également des accesseurs des UserInterface de Player et GameEngine ?

Merci d'avance de vos réponses


Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, lundi 4 janvier 2016, 11:11
 

1) Le booléen sert normalement à indiquer la fin du jeu. On peut sans doute faire autrement et s'en passer, pour pouvoir retourner une String qui serait finalement affichée dans le GameEngine, mais n'aura-t-on jamais à afficher de message intermédiaire au cours de l'exécution d'une commande ?

2) Dans certaines commandes, l'accès à UserInterface (et probablement à GameEngine) sera indispensable. Puisqu'on dispose du Player en paramètre d'execute, on pourrait imaginer des accesseurs dans Player (pour faciliter l'écriture, on peut stocker les références dans des variables locales au début d'execute). On pourrait aussi les stocker dans Command pour éviter d'avoir à refaire la même chose à chaque appel d'execute.

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, mercredi 4 janvier 2017, 22:20
 

Un étudiant a écrit :

Dans l'exercice 7.47 "abstract Command", je voudrais savoir si le fait d'avoir migré le switch de GameEngine vers CommandWords (pour les 2 hashmap) est bien ce qui est attendu?

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, mercredi 4 janvier 2017, 22:25
 

Non, pas du tout, le switch devient désormais inutile.

Veuillez mieux regarder le fonctionnement de zuul-even-better : une seule instruction suffit dans la méthode play pour exécuter n'importe quelle commande !

Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, mercredi 28 avril 2021, 19:35
 

Un étudiant a écrit :

Je me pose une question en rapport avec l'exercice 7.47 qui est la suivante :
Est-il préférable de faire une classe par commande comme montré dans le projet zuul-even-better ou bien de garder la classe CommandWord en type enum ce qui permet de faire moins de modification de code pour ajouter une commande ?
Et comment savoir lequel correspond à une écriture plus correcte ?


Avatar Denis BUREAU
Re: Exercice 7.47
par Denis BUREAU, mercredi 28 avril 2021, 19:41
 

D'abord, cet exercice 7.47 consiste à incorporer la conception de zuul-even-better dans votre jeu, c'est-à-dire une classe par commande !

Ensuite, vous devriez lire la discussion du forum ci-dessus, indiquée par Attention ! dans l'énoncé.